<?php
namespace core;


class Router {
    private Route $curr; //当前路由(路由分组开始时会切换, 分组代码执行完后再切换回来)


    public function __construct() {
        // 全局中间件放到根路由
        $route = new Route('/', '', APP_MIDDLEWARES);

        $this->curr = $route;

        // 加载路由文件(路由文件使用 $router 访问)
        $router = $this;

        foreach (glob(PATH_ROUTE . '*.php') as $routeFile) {
            require_once $routeFile;
        }
    }


    // 开始路由处理
    public function routing(Request $request, Response $response): void {
        $uri = $request->getUri();
        slash_add_end($uri);

        $this->dispatch($this->curr, $request->getMethod(), $uri, $request, $response);

        $response->send();
    }


    // 路由分组
    public function group(string $group, \Closure $cb): void {
        $lastRoute = $this->curr;

        slash_add_head($group);
        $route = new Route($group, $lastRoute->getNamespace(), $lastRoute->getMiddlewares());
        $this->curr->addChild($route);
        $this->curr = $route;

        call_user_func($cb, $this);

        $this->curr = $lastRoute;
    }

    // 设置命名空间
    public function namespace(string $ns): void {
        $this->curr->setNamespace($ns);
    }

    // 设置中间件
    public function middleware(array | string $middleware): void {
        $this->curr->addMiddleware($middleware);
    }


    public function get(string $uri, \Closure | string $cb, $middleware = null): void {
        $this->curr->addRoute('get', $uri, $cb, $middleware);
    }

    public function post(string $uri, \Closure | string $cb, $middleware = null): void {
        $this->curr->addRoute('post', $uri, $cb, $middleware);
    }

    public function put(string $uri, \Closure | string $cb, $middleware = null): void {
        $this->curr->addRoute('put', $uri, $cb, $middleware);
    }

    public function delete(string $uri, \Closure | string $cb, $middleware = null): void {
        $this->curr->addRoute('delete', $uri, $cb, $middleware);
    }

    public function patch(string $uri, \Closure | string $cb, $middleware = null): void {
        $this->curr->addRoute('patch', $uri, $cb, $middleware);
    }

    public function options(string $uri, \Closure | string $cb, $middleware = null): void {
        $this->curr->addRoute('options', $uri, $cb, $middleware);
    }

    public function any(string $uri, \Closure | string $cb, $middleware = null): void {
        $methods = ['get', 'post', 'put', 'delete', 'patch', 'options'];
        foreach ($methods as $method) {
            $this->curr->addRoute($method, $uri, $cb, $middleware);
        }
    }


    private function dispatch(Route $route, string $method, string $uri, Request $request, Response $response): void {
        $group = $route->getGroup();

        // 分组不会有命名参数, 不包含直接返回
        if (!str_starts_with($uri, $group)) return;

        $uri = substr($uri, strlen($group));
        slash_add_head($uri);

        $routes = $route->getRoutes();

        foreach ($routes as $item) {
            if ($item instanceof RouteItem) {
                if ($method == $item->method && $uri == $item->uri) {
                    // 全匹配路由
                    $this->applyMiddleware($route->getMiddlewares(), $item, $route->getNamespace(), $request, $response);

                    return;
                } else if ($method == $item->method  && preg_match_all('(:\w+)', $item->uri, $names)) {
                    // 参数路由
                    $pattern = preg_replace('(:\w+)', '(\w+)', $item->uri);
                    if (preg_match('#^' . $pattern . '$#', $uri, $matches)) {
                        $params = $this->mergeParams($names[0], $matches);
                        $request->setParams($params);

                        $this->applyMiddleware($route->getMiddlewares(), $item, $route->getNamespace(), $request, $response);

                        return;
                    }
                }
            } else {
                $this->dispatch($item, $method, $uri, $request, $response);
            }
        }
    }

    private function applyMiddleware(array $middlewares, RouteItem $item, string $namespace, Request $request, Response $response): void {
        if (!empty($middlewares)) {
            $middleware = array_shift($middlewares);
            $next = function () use ($middlewares, $item, $namespace, $request, $response) {
                $this->applyMiddleware($middlewares, $item, $namespace, $request, $response);
            };
            call_user_func_array([new $middleware(), 'handle'], [$request, $response, $next]);
        } else {
            $this->start($item, $namespace, $request, $response);
        }
    }

    private function start(RouteItem $item, string $namespace, Request $request, Response $response): void {
        if ($item->cb instanceof \Closure) {
            ($item->cb)($request, $response);
        } else {
            [$controller, $method] = explode('@', $item->cb);
            $module = "$namespace\\$controller";
            new $module($method, $item->middleware, $request, $response);
        }
    }

    private function mergeParams(array $names, array $matches): array {
        $params = [];

        for ($i = 0; $i < count($names); $i++) {
            $key = $names[$i];
            $val = $matches[$i + 1];
            $params[substr($key, 1)] = $val;
        }

        return $params;
    }
}
